package com.zhouqinuan.spring.cloud.develop.framework.base.log;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.Order;

import com.zhouqinuan.spring.cloud.develop.framework.base.config.distributed.id.SnowFlakeIdGenerator;
import com.zhouqinuan.spring.cloud.develop.framework.base.config.log.LogProperties;

import lombok.extern.slf4j.Slf4j;

@Order(1)
@Slf4j
public class LogTraceFilter implements Filter, ApplicationContextAware {

    private static final String TRACE_ID = "traceId"; // 日志跟踪ID

    private static final String BEGIN_TIME_ATTRIBUTE = "BEGIN_TIME_ATTRIBUTE"; // 请求开始时间

    private static final String REQ_BODY_ATTRIBUTE = "REQ_BODY_ATTRIBUTE"; // 请求body内容

    private static final String RESP_BODY_ATTRIBUTE = "RESP_BODY_ATTRIBUTE"; // 响应body内容

    private ApplicationContext applicationContext;

    private LogProperties logProperties;

    public void setLogProperties(LogProperties logProperties) {
        this.logProperties = logProperties;
    }

    // 排除哪些因先读取请求体导致无法正常访问的问题
    public boolean isExclude(String requestPath) {
        if (logProperties != null && CollectionUtils.isNotEmpty(logProperties.getExcludePathList())) {
            if (logProperties.getExcludePathList().contains(requestPath)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        doFilterInternal(request, response, chain);
    }

    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;

        try {

            ServletRequest servletRequest = preHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Invoked preHandle method.  servletRequest?: [" + servletRequest.getClass() + "]");
            }

            executeChain(servletRequest != null ? servletRequest : request, response, chain);

            postHandle(request, response);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked postHandle method");
            }

        } catch (Exception e) {
            exception = e;
        } finally {
            cleanup(request, response, exception);
        }
    }

    protected ServletRequest preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        req.setAttribute(BEGIN_TIME_ATTRIBUTE, System.currentTimeMillis());

        SnowFlakeIdGenerator snowFlakeIdGenerator = applicationContext.getBean(SnowFlakeIdGenerator.class);
        String traceId = snowFlakeIdGenerator.nextId() + "";
        org.slf4j.MDC.put(TRACE_ID, traceId);

        // 记录请求参数
        if (request instanceof HttpServletRequest && !isExclude(req.getRequestURI())) {
            ServletRequest requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) request);
            req.setAttribute(REQ_BODY_ATTRIBUTE, ((MyHttpServletRequestWrapper) requestWrapper).getBody());

            return requestWrapper;
        }

        return null;
    }

    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
        chain.doFilter(request, response);
    }

    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {

    }

    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception)
            throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        long beginTime = (long) req.getAttribute(BEGIN_TIME_ATTRIBUTE);

        String path = req.getRequestURI();
        StringBuilder stringBuilder = new StringBuilder();
        request.getParameterMap().forEach((key, value) -> {
            stringBuilder.append(key + "=" + value[0] + "&");
        });

        String reqStr = "";
        if (stringBuilder.length() > 0) {
            reqStr = stringBuilder.toString();
        }

        String reqBodyMsg = (String) req.getAttribute(REQ_BODY_ATTRIBUTE);
        String respBodyMsg = (String) req.getAttribute(RESP_BODY_ATTRIBUTE);
        if (StringUtils.isNoneBlank(respBodyMsg)) {
            log.info("请求链接:{}{}, 请求参数:{}， 响应结果:{}，耗时:{}ms", path,
                    StringUtils.isEmpty(req.getQueryString()) ? "" : "?" + req.getQueryString(),
                    StringUtils.isNoneBlank(reqBodyMsg) ? reqBodyMsg : reqStr, respBodyMsg,
                    System.currentTimeMillis() - beginTime);
        } else {
            log.info("请求链接:{}{}, 请求参数:{}， 响应结果:{}，耗时:{}ms", path,
                    StringUtils.isEmpty(req.getQueryString()) ? "" : "?" + req.getQueryString(),
                    StringUtils.isNoneBlank(reqBodyMsg) ? reqBodyMsg : reqStr, "",
                    System.currentTimeMillis() - beginTime);
        }
    }

    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
            throws ServletException, IOException {
        Exception exception = existing;
        try {
            afterCompletion(request, response, exception);
            if (log.isTraceEnabled()) {
                log.trace("Successfully invoked afterCompletion method.");
            }
        } catch (Exception e) {
            if (exception == null) {
                exception = e;
            } else {
                log.debug("afterCompletion implementation threw an exception.  This will be ignored to "
                        + "allow the original source exception to be propagated.", e);
            }
        }

        try {
            if (exception != null) {
                if (exception instanceof ServletException) {
                    throw (ServletException) exception;
                } else if (exception instanceof IOException) {
                    throw (IOException) exception;
                } else {
                    if (log.isDebugEnabled()) {
                        String msg = "Filter execution resulted in an unexpected Exception "
                                + "(not IOException or ServletException as the Filter API recommends).  "
                                + "Wrapping in ServletException and propagating.";
                        log.debug(msg);
                    }
                    throw new ServletException(exception);
                }
            }
        } finally {
            org.slf4j.MDC.clear();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void destroy() {
        org.slf4j.MDC.clear();
    }
}
